﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Caliburn.Micro;
using Adalbertus.BudgetPlanner.ViewModels.ExternalSources;
using Adalbertus.BudgetPlanner.Services;
using Adalbertus.BudgetPlanner.Domain;
using System.Diagnostics.Contracts;
using System.Windows;

namespace Adalbertus.BudgetPlanner.ViewModels.BudgetPlanning
{
    public class RevenuesViewModel : Screen
    {
        private IIncomeService _incomeService;
        private ISavingService _savingService;
        private IBudgetService _budgetService;
        public RevenuesViewModel(IIncomeService incomeService, ISavingService savingService, IBudgetService budgetService)
        {
            Contract.Requires<ArgumentNullException>(incomeService != null, "incomeService");
            Contract.Requires<ArgumentNullException>(savingService != null, "savingService");
            Contract.Requires<ArgumentNullException>(budgetService != null, "budgetService");

            _incomeService = incomeService;
            _savingService = savingService;
            _budgetService = budgetService;

            _availableIncomes = new BindableCollection<IncomeVM>();
            _availableSavings = new BindableCollection<SavingVM>();

            IncomeEntryDate = DateTime.Now;
            SavingEntryDate = DateTime.Now;

            BudgetIncomes = new BindableCollection<IncomeEntryVM>();
            BudgetIncomes.CollectionChanged += BudgetIncomesCollectionChanged;

            BudgetSavings = new BindableCollection<SavingEntryVM>();
            BudgetSavings.CollectionChanged += BudgetSavingsCollectionChanged;
        }

        public BudgetVM Budget { get; set; }

        public string TotalSumOfRevenues
        {
            get
            {
                return string.Format("{0:c2}", BudgetIncomes.Sum(x => x.Value) + BudgetSavings.Sum(x => x.Value));
            }
        }

        #region Incomes
        private BindableCollection<IncomeVM> _availableIncomes;
        public BindableCollection<IncomeVM> AvailableIncomes
        {
            get
            {
                Contract.Ensures(Contract.Result<BindableCollection<IncomeVM>>() != null);
                return _availableIncomes;
            }
        }

        private IncomeVM _selectedAvailableIncome;
        public IncomeVM SelectedAvailableIncome
        {
            get { return _selectedAvailableIncome; }
            set
            {
                _selectedAvailableIncome = value;
                NotifyOfPropertyChange(() => SelectedAvailableIncome);
                NotifyOfPropertyChange(() => CanAddIncomeEntry);
            }
        }

        private DateTime _incomeEntryDate;
        public DateTime IncomeEntryDate
        {
            get { return _incomeEntryDate; }
            set
            {
                _incomeEntryDate = value;
                NotifyOfPropertyChange(() => IncomeEntryDate);
                NotifyOfPropertyChange(() => CanAddIncomeEntry);
            }
        }

        private decimal _incomeEntryValue;
        public decimal IncomeEntryValue
        {
            get { return _incomeEntryValue; }
            set
            {
                _incomeEntryValue = value;
                NotifyOfPropertyChange(() => IncomeEntryValue);
                NotifyOfPropertyChange(() => CanAddIncomeEntry);
            }
        }

        public string SumOfIncomes
        {
            get
            {
                return string.Format("{0:c2}", BudgetIncomes.Sum(x => x.Value));
            }
        }
        public BindableCollection<IncomeEntryVM> BudgetIncomes { get; set; }

        public bool CanAddIncomeEntry
        {
            get
            {
                bool isIncomeSelected = SelectedAvailableIncome != null;
                bool isIncomeEntryValueNotZero = IncomeEntryValue > 0;

                return isIncomeSelected && isIncomeEntryValueNotZero;
            }
        }

        public void AddIncomeEntry()
        {
            if (Budget == null)
            {
                throw new InvalidOperationException("Unable to find Budget");
            }

            if (SelectedAvailableIncome != null)
            {
                IncomeEntry addedIncomeEntry = Budget.WrappedBudget.AddIncomeEntry(SelectedAvailableIncome.WrappedIncome, IncomeEntryValue, IncomeEntryDate);
                _budgetService.Save(Budget.WrappedBudget);
                BudgetIncomes.Add(new IncomeEntryVM(addedIncomeEntry, SelectedAvailableIncome));

                IncomeEntryDate = DateTime.Now;
                IncomeEntryValue = 0;
                AvailableIncomes.Remove(SelectedAvailableIncome);
                SelectedAvailableIncome = null;
            }
        }

        public void RemoveIncomeEntry(IncomeEntryVM incomeEntry)
        {
            // TODO: I guess it is a bug in caliburn - this method is invoked not from BudgetPlanView
            // but from RevenuesView, unfortunatelly caliburn think it was invoked from BudgetPlanView
            Contract.Requires<ArgumentNullException>(incomeEntry != null, "incomeEntry");

            if (Budget == null)
            {
                throw new InvalidOperationException("Unable to find Budget");
            }

            Budget.WrappedBudget.RemoveIncomeEntry(incomeEntry.WrappedIncomeEntry);
            _budgetService.Save(Budget.WrappedBudget);
            BudgetIncomes.Remove(incomeEntry);
            AvailableIncomes.Add(incomeEntry.Parent);
        }

        private void BudgetIncomesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            NotifyOfPropertyChange(() => SumOfIncomes);
            NotifyOfPropertyChange(() => TotalSumOfRevenues);
        }

        #endregion

        #region Savings
        private BindableCollection<SavingVM> _availableSavings;
        public BindableCollection<SavingVM> AvailableSavings
        {
            get
            {
                Contract.Ensures(Contract.Result<BindableCollection<SavingVM>>() != null);
                return _availableSavings;
            }
        }

        private SavingVM _selectedAvailableSaving;
        public SavingVM SelectedAvailableSaving
        {
            get { return _selectedAvailableSaving; }
            set
            {
                _selectedAvailableSaving = value;
                NotifyOfPropertyChange(() => SelectedAvailableSaving);
                NotifyOfPropertyChange(() => CanAddSavingEntry);
            }
        }

        public bool CanAddSavingEntry
        {
            get
            {
                bool isSavingSelected = SelectedAvailableSaving != null;
                bool isSavingEntryValueNotZero = SavingEntryValue > 0;

                return (isSavingSelected && isSavingEntryValueNotZero);
            }
        }

        private DateTime _savingEntryDate;
        public DateTime SavingEntryDate
        {
            get { return _savingEntryDate; }
            set
            {
                _savingEntryDate = value;
                NotifyOfPropertyChange(() => SavingEntryDate);
                NotifyOfPropertyChange(() => CanAddSavingEntry);
            }
        }

        private decimal _savingEntryValue;
        public decimal SavingEntryValue
        {
            get { return _savingEntryValue; }
            set
            {
                _savingEntryValue = value;
                NotifyOfPropertyChange(() => SavingEntryValue);
                NotifyOfPropertyChange(() => CanAddSavingEntry);
            }
        }

        public string SumOfSavings
        {
            get
            {
                return string.Format("{0:c2}", BudgetSavings.Sum(x => x.Value));
            }
        }

        public BindableCollection<SavingEntryVM> BudgetSavings { get; set; }

        public void AddSavingEntry()
        {
            if (Budget == null)
            {
                throw new InvalidOperationException("Unable to find Budget");
            }

            if (SelectedAvailableSaving != null)
            {
                SavingEntry addedSavingEntry = Budget.WrappedBudget.WithdrawSavingEntry(SelectedAvailableSaving.WrappedSaving, SavingEntryValue, SavingEntryDate);
                _budgetService.Save(Budget.WrappedBudget);
                BudgetSavings.Add(new SavingEntryVM(addedSavingEntry, SelectedAvailableSaving) { IsWithdrawPresentationDisabled = true });

                SavingEntryDate = DateTime.Now;
                SavingEntryValue = 0;
                AvailableSavings.Remove(SelectedAvailableSaving);
                SelectedAvailableSaving = null;
            }
        }

        public void RemoveSavingEntry(SavingEntryVM savingEntry)
        {
            // TODO: I guess it is a bug in caliburn - this method is invoked not from BudgetPlanView
            // but from RevenuesView, unfortunatelly caliburn think it was invoked from BudgetPlanView
            Contract.Requires<ArgumentNullException>(savingEntry != null, "savingEntry");

            if (Budget == null)
            {
                throw new InvalidOperationException("Unable to find Budget");
            }

            Budget.WrappedBudget.CancelWithdrawSavingEntry(savingEntry.WrappedSavingEntry);
            _budgetService.Save(Budget.WrappedBudget);
            BudgetSavings.Remove(savingEntry);
            AvailableSavings.Add(savingEntry.Parent);
        }

        private void BudgetSavingsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            NotifyOfPropertyChange(() => SumOfSavings);
            NotifyOfPropertyChange(() => TotalSumOfRevenues);
        }
        #endregion

        protected override void OnActivate()
        {
            if (Budget == null)
            {
                throw new InvalidOperationException("Cannot activate Revenue ViewModel - Budget is not set.");
            }

            BudgetIncomes.Clear();
            BudgetIncomes.AddRange(Budget.Incomes);
            BudgetSavings.Clear();
            BudgetSavings.AddRange(Budget.Savings);

            IEnumerable<Income> allIncomes = _incomeService.GetAll().Where(x => !BudgetIncomes.Any(y => y.Name == x.Name));
            AvailableIncomes.Clear();
            AvailableIncomes.AddRange(allIncomes.Select(x => new IncomeVM(x)).ToArray());

            IEnumerable<Saving> allSavings = _savingService.GetAll().Where(x => !BudgetSavings.Any(y => y.Name == x.Name));
            AvailableSavings.Clear();
            AvailableSavings.AddRange(allSavings.Select(x => new SavingVM(x)).ToArray());

            SelectedAvailableIncome = null;
        }
    }
}
